home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / DELPHI32 / COMM / VSSCM32 / VSSCOMM3.PAS < prev    next >
Pascal/Delphi Source File  |  1996-06-19  |  48KB  |  1,600 lines

  1. unit VSSComm32;
  2. {** This Communications Component is based on David Wann's COMM32 component.
  3.     I have added a DataBits property, which Davids lacked, and as one of my
  4.     apps need Databits, I set out to add Databits to Davids base componet.
  5.     This is a bit of a feat for me, as I have only written one component before
  6.     this, and even that is still under construction.
  7.     I have also made the component abit more user freindly, by adding dropdown
  8.     lists of comm ports and Baudrates, as well as to the new Databits.
  9.     As with David Wann's Comm32 component, this is freeware.
  10.  
  11.     I hope you find this Component useful, as much as I do.
  12.  
  13.     Cheers, Jeremy Coulter
  14.             Visual Software Soultions
  15.             vss@mac.co.nz
  16.             http://www.voyager.co.nz/~jcoulter/index.htm
  17.  
  18.     thanks to :-
  19.     David Wann
  20.     Stamina Software
  21.     28/02/96
  22.     davidwann@hunterlink.net.au **}
  23.  
  24. interface
  25.  
  26. uses
  27.     Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  28.     Misc;
  29.  
  30. const
  31.     // messages from read/write threads
  32.     PWM_GOTCOMMDATA = WM_USER + 1;
  33.     PWM_REQUESTHANGUP = WM_USER + 2;
  34.  
  35. type
  36.     ECommsError = class( Exception );
  37.  
  38.         TCommPort = (Com1, Com2, Com3, Com4);
  39.     TParity = (None,Odd,Even,Mark,Space);
  40.     TStopBits = (_1, _1_5, _2);
  41.         TDataBits = (_4,_5,_6,_7,_8);
  42.         TBaudRate = (____110, ____300, ____600, ___1200, ___2400, ___4800, ___9600, __14400,
  43.                          __19200, __38400, __56000, _128000, _256000);
  44.     TReadThread = class( TThread )
  45.     protected
  46.         procedure Execute; override;
  47.     public
  48.         hCommFile:             THandle;
  49.         hCloseEvent:        THandle;
  50.         hVSSComm32Window:        THandle;
  51.         function SetupCommEvent( lpOverlappedCommEvent: POverlapped;
  52.                         var lpfdwEvtMask: DWORD ): Boolean;
  53.         function SetupReadEvent( lpOverlappedRead: POverlapped;
  54.                         lpszInputBuffer: LPSTR; dwSizeofBuffer: DWORD;
  55.                         var lpnNumberOfBytesRead: DWORD ): Boolean;
  56.         function HandleCommEvent( lpOverlappedCommEvent: POverlapped;
  57.                         var lpfdwEvtMask: DWORD; fRetrieveEvent: Boolean ): Boolean;
  58.         function HandleReadEvent( lpOverlappedRead: POverlapped;
  59.                         lpszInputBuffer: LPSTR; dwSizeofBuffer: DWORD;
  60.                         var lpnNumberOfBytesRead: DWORD ): Boolean;
  61.         function HandleReadData( lpszInputBuffer: LPCSTR; dwSizeofBuffer: DWORD ): Boolean;
  62.         function ReceiveData( lpNewString: LPSTR; dwSizeofNewString: DWORD ): BOOL;
  63.         procedure PostHangupCall;
  64.     end;
  65.  
  66.     TWriteThread = class( TThread )
  67.     protected
  68.         procedure Execute; override;
  69.         function HandleWriteData( lpOverlappedWrite: POverlapped;
  70.                 pDataToWrite: PChar; dwNumberOfBytesToWrite: DWORD): Boolean;
  71.     public
  72.         hCommFile:             THandle;
  73.         hCloseEvent:        THandle;
  74.         hVSSComm32Window:        THandle;
  75.         function WriteComm( pDataToWrite: LPCSTR; dwSizeofDataToWrite: DWORD ): Boolean;
  76.         procedure PostHangupCall;
  77.     end;
  78.  
  79.     TReceiveDataEvent = procedure( Buffer: Pointer; BufferLength: Word ) of object;
  80.  
  81.     TVSSComm32 = class( TComponent )
  82.     private
  83.         { Private declarations }
  84.         ReadThread:                TReadThread;
  85.         WriteThread:            TWriteThread;
  86.         FCommsLogFileName,
  87.         FCommPort:            TCommport;
  88.         hCommFile:             THandle;
  89.         hCloseEvent:            THandle;
  90.         FOnReceiveData:         TReceiveDataEvent;
  91.         FOnRequestHangup:        TNotifyEvent;
  92.         FHWnd:                    THandle;
  93.         FBaudRate:            TBaudrate;
  94.         FParity:                 TParity;
  95.         FStopBits:            TStopBits;
  96.                 FDataBits:                      TDatabits;
  97.  
  98.         procedure SetCommsLogFileName( LogFileName: string );
  99.         function GetReceiveDataEvent: TReceiveDataEvent;
  100.         procedure SetReceiveDataEvent( AReceiveDataEvent: TReceiveDataEvent );
  101.         function GetRequestHangupEvent: TNotifyEvent;
  102.         procedure SetRequestHangupEvent( ARequestHangupEvent: TNotifyEvent );
  103.         procedure CommWndProc( var msg: TMessage );
  104.     protected
  105.         { Protected declarations }
  106.         procedure CloseReadThread;
  107.         procedure CloseWriteThread;
  108.         procedure ReceiveData( Buffer: PChar; BufferLength: Word );
  109.         procedure RequestHangup;
  110.     public
  111.         { Public declarations }
  112.         constructor Create( AOwner: TComponent ); override;
  113.         destructor Destroy; override;
  114.         function StartComm: Boolean;
  115.         procedure StopComm;
  116.         function WriteCommData( pDataToWrite: PChar; dwSizeofDataToWrite: Word ): Boolean;
  117.     published
  118.         { Published declarations }
  119.                 property CommPort: Tcommport read FCommPort write FCommPort;
  120.         property BaudRate: TBaudRate read FBaudRate write FBaudRate;
  121.         property Parity: TParity read FParity write FParity;
  122.                 Property DataBits: TDatabits read FDatabits write FDatabits;
  123.         property StopBits: TStopBits read FStopBits write FStopBits;
  124.         {property CommPort: string read FCommPort write FCommPort;}
  125.            {    property CommsLogFileName: string read FCommsLogFileName write SetCommsLogFileName;}
  126.         property OnReceiveData: TReceiveDataEvent
  127.                 read GetReceiveDataEvent write SetReceiveDataEvent;
  128.         property OnRequestHangup: TNotifyEvent
  129.                 read GetRequestHangupEvent write SetRequestHangupEvent;
  130.     end;
  131.  
  132. const
  133. // This is the message posted to the WriteThread
  134. // When we have something to write.
  135.     PWM_COMMWRITE = WM_USER+1;
  136.  
  137. // Default size of the Input Buffer used by this code.
  138.     INPUTBUFFERSIZE = 2048;
  139.  
  140. var
  141.     CommsLogFile:    Text; // means you can only debug 1 component at a time
  142.  
  143.  
  144. procedure LogDebugInfo( outstr: PChar );
  145. procedure LogDebugLastError( dwLastError: DWORD; szPrefix: LPSTR );
  146. procedure Register;
  147.  
  148. implementation
  149. {$R VSSComm32.res}
  150. var
  151.     CommsLogName:    string; // used as a check if file is assigned
  152.  
  153. (******************************************************************************)
  154. //                                    TVSSComm32 PUBLIC METHODS
  155. (******************************************************************************)
  156.  
  157. constructor TVSSComm32.Create( AOwner: TComponent );
  158. begin
  159.     inherited Create( AOwner );
  160.     FCommPort := COM1;
  161.     {FCommsLogFileName := '';}
  162.     CommsLogName := '';
  163.     ReadThread := nil;
  164.     WriteThread := nil;
  165.     hCommFile := 0;
  166.     if not (csDesigning in ComponentState) then
  167.         FHWnd := AllocateHWnd(CommWndProc);
  168. end;
  169.  
  170. destructor TVSSComm32.Destroy;
  171. begin
  172.     if not (csDesigning in ComponentState) then
  173.     begin
  174.         DeallocateHWnd(FHwnd);
  175.     end;
  176.     inherited Destroy;
  177. end;
  178.  
  179. //
  180. //  FUNCTION: StartComm
  181. //
  182. //  PURPOSE: Starts communications over the comm port.
  183. //
  184. //  PARAMETERS:
  185. //    hNewCommFile - This is the COMM File handle to communicate with.
  186. //                   This handle is obtained from TAPI.
  187. //
  188. //  RETURN VALUE:
  189. //    TRUE if able to setup the communications.
  190. //
  191. //  COMMENTS:
  192. //
  193. //    StartComm makes sure there isn't communication in progress already,
  194. //    creates a Comm file, and creates the read and write threads.  It
  195. //    also configures the hNewCommFile for the appropriate COMM settings.
  196. //
  197. //    If StartComm fails for any reason, it's up to the calling application
  198. //    to close the Comm file handle.
  199. //
  200. //
  201.  
  202. function TVSSComm32.StartComm: Boolean;
  203. var
  204.     commtimeouts:    TCommTimeouts;
  205.     dcb:                Tdcb;
  206.     commprop:        TCommProp;
  207.     fdwEvtMask:        DWORD;
  208.     hNewCommFile: THandle;
  209.         dbits:        TDatabits;
  210.         brate:         TBaudrate;
  211.         cmprt:         TCommPort;
  212.         setfcommport:  string;
  213. begin
  214.     // Are we already doing comm?
  215.     if (hCommFile <> 0) then
  216.         raise ECommsError.Create( 'Already have a comm file open' );
  217.  
  218.     {if CommsLogFileName <> '' then
  219.     begin
  220.         AssignFile( CommsLogFile, fCommsLogFileName );
  221.         Rewrite( CommsLogFile );
  222.     end;}
  223.         {** set comm port **}
  224.         cmprt:=FCommport;
  225.         case cmprt of
  226.              com1: setfcommport:='COM1';
  227.              com2: setfcommport:='COM2';
  228.              com3: setfcommport:='COM3';
  229.              com4: setfcommport:='COM4';
  230.         end;
  231.  
  232.     hNewCommFile := CreateFile(
  233.                             PChar(setfCommPort),
  234.                             GENERIC_READ+GENERIC_WRITE,
  235.                             0, {not shared}
  236.                             nil, {no security ??}
  237.                             OPEN_EXISTING,
  238.                             {FILE_ATTRIBUTE_NORMAL+}FILE_FLAG_OVERLAPPED,
  239.                             0 {template} );
  240.     if hNewCommFile = INVALID_HANDLE_VALUE then
  241.         raise ECommsError.Create( 'Error opening com port' );
  242.  
  243.     // Is this a valid comm handle?
  244.     if GetFileType( hNewCommFile ) <> FILE_TYPE_CHAR then
  245.         raise ECommsError.Create( 'File handle is not a comm handle. ' );
  246.  
  247.     // Its ok to continue.
  248.  
  249.     hCommFile := hNewCommFile;
  250.  
  251.     // Setting and querying the comm port configurations.
  252.  
  253.     // Configure the comm settings.
  254.     // NOTE: Most Comm settings can be set through TAPI, but this means that
  255.     //       the CommFile will have to be passed to this component.
  256.  
  257.     GetCommState( hNewCommFile, dcb );
  258.     GetCommProperties( hNewCommFile, commprop );
  259.     GetCommMask( hCommFile, fdwEvtMask );
  260.     GetCommTimeouts( hCommFile, commtimeouts );
  261.  
  262.     // The CommTimeout numbers will very likely change if you are
  263.     // coding to meet some kind of specification where
  264.     // you need to reply within a certain amount of time after
  265.     // recieving the last byte.  However,  If 1/4th of a second
  266.     // goes by between recieving two characters, its a good
  267.     // indication that the transmitting end has finished, even
  268.     // assuming a 1200 baud modem.
  269.  
  270.     commtimeouts.ReadIntervalTimeout         := 250;
  271.     commtimeouts.ReadTotalTimeoutMultiplier  := 0;
  272.     commtimeouts.ReadTotalTimeoutConstant    := 0;
  273.     commtimeouts.WriteTotalTimeoutMultiplier := 0;
  274.     commtimeouts.WriteTotalTimeoutConstant   := 0;
  275.  
  276.     SetCommTimeouts( hCommFile, commtimeouts );
  277.  
  278.     // fAbortOnError is the only DCB dependancy in TapiComm.
  279.     // Can't guarentee that the SP will set this to what we expect.
  280.     {dcb.fAbortOnError := False; NOT VALID}
  281.         dbits:=FDatabits;
  282.         brate:= FBaudrate;
  283.         {** Set Baud Rate **}
  284.     case brate of
  285.             ____110: DCB.BaudRate := CBR_110;
  286.             ____300: DCB.BaudRate := CBR_300;
  287.             ____600: DCB.BaudRate := CBR_600;
  288.             ___1200: DCB.BaudRate := CBR_1200;
  289.             ___2400: DCB.BaudRate := CBR_2400;
  290.             ___4800: DCB.BaudRate := CBR_4800;
  291.             ___9600: DCB.BaudRate := CBR_9600;
  292.             __14400: DCB.BaudRate := CBR_14400;
  293.             __19200: DCB.BaudRate := CBR_19200;
  294.             __38400: DCB.BaudRate := CBR_38400;
  295.             __56000: DCB.BaudRate := CBR_56000;
  296.             _128000: DCB.BaudRate := CBR_128000;
  297.             _256000: DCB.BaudRate := CBR_256000;
  298.          end;
  299.         {** set partiy **}
  300.     dcb.Parity := Ord(FParity);
  301.  
  302.         {** set databits **}
  303.         case dbits of
  304.         _4: DCB.ByteSize := 4;
  305.         _5: DCB.ByteSize := 5;
  306.         _6: DCB.ByteSize := 6;
  307.         _7: DCB.ByteSize := 7;
  308.         _8: DCB.ByteSize := 8;
  309.     end;
  310.         {** set stopbits**}
  311.     dcb.StopBits := Ord(FStopBits);
  312.  
  313.         SetCommState( hNewCommFile, dcb );
  314.  
  315.     // Create the event that will signal the threads to close.
  316.     hCloseEvent := CreateEvent( nil, True, False, nil );
  317.  
  318.     if hCloseEvent = 0 then
  319.     begin
  320.          LogDebugLastError( GetLastError, 'Unable to CreateEvent: ' );
  321.          hCommFile := 0;
  322.          Result := False;
  323.          Exit
  324.     end;
  325.  
  326.     // Create the Read thread.
  327.     try
  328.         ReadThread := TReadThread.Create( True {suspended} );
  329.     except
  330.         LogDebugLastError( GetLastError, 'Unable to create Read thread' );
  331.         raise ECommsError.Create( 'Unable to create Read thread' );
  332.     end;
  333.     ReadThread.hCommFile := hCommFile;
  334.     ReadThread.hCloseEvent := hCloseEvent;
  335.     ReadThread.hVSSComm32Window := FHWnd;
  336.     ReadThread.Resume;
  337.  
  338.     // Comm threads should have a higher base priority than the UI thread.
  339.     // If they don't, then any temporary priority boost the UI thread gains
  340.     // could cause the COMM threads to loose data.
  341.     ReadThread.Priority := tpHighest;
  342.  
  343.     // Create the Write thread.
  344.     try
  345.         WriteThread := TWriteThread.Create( True {suspended} );
  346.     except
  347.         LogDebugLastError( GetLastError, 'Unable to create Write thread' );
  348.         raise ECommsError.Create( 'Unable to create Write thread' );
  349.     end;
  350.     WriteThread.hCommFile := hCommFile;
  351.     WriteThread.hCloseEvent := hCloseEvent;
  352.     WriteThread.hVSSComm32Window := FHWnd;
  353.     WriteThread.Resume;
  354.  
  355.     WriteThread.Priority := tpHigher;
  356.  
  357.     // Everything was created ok.  Ready to go!
  358.     Result := True;
  359. end; {TVSSComm32.StartComm}
  360.  
  361. //
  362. //  FUNCTION: StopComm
  363. //
  364. //  PURPOSE: Stop and end all communication threads.
  365. //
  366. //  PARAMETERS:
  367. //    none
  368. //
  369. //  RETURN VALUE:
  370. //    none
  371. //
  372. //  COMMENTS:
  373. //
  374. //    Tries to gracefully signal all communication threads to
  375. //    close, but terminates them if it has to.
  376. //
  377. //
  378. procedure TVSSComm32.StopComm;
  379. begin
  380.     // No need to continue if we're not communicating.
  381.     if hCommFile = 0 then
  382.         Exit;
  383.  
  384.     LogDebugInfo( 'Stopping the Comm' );
  385.  
  386.      // Close the threads.
  387.     CloseReadThread;
  388.     CloseWriteThread;
  389.  
  390.     // Not needed anymore.
  391.     CloseHandle( hCloseEvent );
  392.  
  393.     // Now close the comm port handle.
  394.     CloseHandle( hCommFile );
  395.     hCommFile := 0;
  396.     {if fCommsLogFileName <> '' then
  397.         CloseFile( CommsLogFile ); }
  398. end; {TVSSComm32.StopComm}
  399.  
  400. //
  401. //  FUNCTION: WriteCommData(PChar, Word)
  402. //
  403. //  PURPOSE: Send a String to the Write Thread to be written to the Comm.
  404. //
  405. //  PARAMETERS:
  406. //    pszStringToWrite     - String to Write to Comm port.
  407. //    nSizeofStringToWrite - length of pszStringToWrite.
  408. //
  409. //  RETURN VALUE:
  410. //    Returns TRUE if the PostMessage is successful.
  411. //    Returns FALSE if PostMessage fails or Write thread doesn't exist.
  412. //
  413. //  COMMENTS:
  414. //
  415. //    This is a wrapper function so that other modules don't care that
  416. //    Comm writing is done via PostMessage to a Write thread.  Note that
  417. //    using PostMessage speeds up response to the UI (very little delay to
  418. //    'write' a string) and provides a natural buffer if the comm is slow
  419. //    (ie:  the messages just pile up in the message queue).
  420. //
  421. //    Note that it is assumed that pszStringToWrite is allocated with
  422. //    LocalAlloc, and that if WriteCommData succeeds, its the job of the
  423. //    Write thread to LocalFree it.  If WriteCommData fails, then its
  424. //    the job of the calling function to free the string.
  425. //
  426. //
  427. function TVSSComm32.WriteCommData( pDataToWrite: PChar; dwSizeofDataToWrite: Word ): Boolean;
  428. var
  429.     Buffer:    Pointer;
  430. begin
  431.     if WriteThread <> nil then
  432.     begin
  433.         Buffer := Pointer(LocalAlloc( LPTR, dwSizeofDataToWrite+1 ));
  434.         Move( pDataToWrite^, Buffer^, dwSizeofDataToWrite );
  435.         if PostThreadMessage( WriteThread.ThreadID, PWM_COMMWRITE,
  436.                      WPARAM(dwSizeofDataToWrite), LPARAM(Buffer) ) then
  437.         begin
  438.             Result := true;
  439.             Exit;
  440.         end
  441.         else
  442.             LogDebugInfo( 'Failed to Post to Write thread. ' );
  443.     end
  444.     else
  445.         LogDebugInfo( 'Write thread not created' );
  446.  
  447.     Result := False;
  448. end; {TVSSComm32.WriteCommData}
  449.  
  450. (******************************************************************************)
  451. //                                    TVSSComm32 PROTECTED METHODS
  452. (******************************************************************************)
  453.  
  454. //
  455. //  FUNCTION: CloseReadThread
  456. //
  457. //  PURPOSE: Close the Read Thread.
  458. //
  459. //  PARAMETERS:
  460. //    none
  461. //
  462. //  RETURN VALUE:
  463. //    none
  464. //
  465. //  COMMENTS:
  466. //
  467. //    Closes the Read thread by signaling the CloseEvent.
  468. //    Purges any outstanding reads on the comm port.
  469. //
  470. //    Note that terminating a thread leaks memory.
  471. //    Besides the normal leak incurred, there is an event object
  472. //    that doesn't get closed.  This isn't worth worrying about
  473. //    since it shouldn't happen anyway.
  474. //
  475. //
  476. procedure TVSSComm32.CloseReadThread;
  477. begin
  478.     // If it exists...
  479.     if ReadThread <> nil then
  480.     begin
  481.         LogDebugInfo( 'Closing Read Thread ');
  482.  
  483.         // Signal the event to close the worker threads.
  484.         SetEvent( hCloseEvent );
  485.  
  486.         // Purge all outstanding reads
  487.         PurgeComm( hCommFile, PURGE_RXABORT + PURGE_RXCLEAR );
  488.  
  489.         // Wait 10 seconds for it to exit.  Shouldn't happen.
  490.         if (WaitForSingleObject(ReadThread.Handle, 10000) = WAIT_TIMEOUT) then
  491.         begin
  492.             LogDebugInfo( 'Read thread not exiting.  Terminating it.' );
  493.             ReadThread.Terminate;
  494.         end;
  495.         ReadThread.Free;
  496.         ReadThread := nil;
  497.     end;
  498. end; {TVSSComm32.CloseReadThread}
  499.  
  500.  
  501. //
  502. //  FUNCTION: CloseWriteThread
  503. //
  504. //  PURPOSE: Closes the Write Thread.
  505. //
  506. //  PARAMETERS:
  507. //    none
  508. //
  509. //  RETURN VALUE:
  510. //    none
  511. //
  512. //  COMMENTS:
  513. //
  514. //    Closes the write thread by signaling the CloseEvent.
  515. //    Purges any outstanding writes on the comm port.
  516. //
  517. //    Note that terminating a thread leaks memory.
  518. //    Besides the normal leak incurred, there is an event object
  519. //    that doesn't get closed.  This isn't worth worrying about
  520. //    since it shouldn't happen anyway.
  521. //
  522. //
  523. procedure TVSSComm32.CloseWriteThread;
  524. begin
  525.     // If it exists...
  526.     if WriteThread <> nil then
  527.     begin
  528.         LogDebugInfo( 'Closing Write Thread' );
  529.  
  530.         // Signal the event to close the worker threads.
  531.         SetEvent(hCloseEvent);
  532.  
  533.         // Purge all outstanding writes.
  534.         PurgeComm(hCommFile, PURGE_TXABORT + PURGE_TXCLEAR);
  535.  
  536.         // Wait 10 seconds for it to exit.  Shouldn't happen.
  537.         if WaitForSingleObject( WriteThread.Handle, 10000 ) = WAIT_TIMEOUT then
  538.         begin
  539.             LogDebugInfo( 'Write thread not exiting.  Terminating it.' );
  540.             WriteThread.Terminate;
  541.         end;
  542.         WriteThread.Free;
  543.         WriteThread := nil;
  544.     end;
  545. end; {TVSSComm32.CloseWriteThread}
  546.  
  547. procedure TVSSComm32.ReceiveData( Buffer: PChar; BufferLength: Word );
  548. begin
  549.     if Assigned(FOnReceiveData) then
  550.         FOnReceiveData( Buffer, BufferLength );
  551. end;
  552.  
  553. procedure TVSSComm32.RequestHangup;
  554. begin
  555.     if Assigned(FOnRequestHangup) then
  556.         FOnRequestHangup( Self );
  557. end;
  558.  
  559. (******************************************************************************)
  560. //                                    TVSSComm32 PRIVATE METHODS
  561. (******************************************************************************)
  562.  
  563. procedure TVSSComm32.SetCommsLogFileName( LogFileName: string );
  564. begin
  565.     CommsLogName := LogFileName;
  566. {    FCommsLogFileName := LogFileName;}
  567. end;
  568.  
  569. procedure TVSSComm32.CommWndProc( var msg: TMessage );
  570. begin
  571.     case msg.msg of
  572.         PWM_GOTCOMMDATA:
  573.         begin
  574.             ReceiveData( PChar(msg.LParam), msg.WParam );
  575.             LocalFree( msg.LParam );
  576.         end;
  577.         PWM_REQUESTHANGUP:
  578.             RequestHangup;
  579.     end;
  580. end;
  581.  
  582. function TVSSComm32.GetReceiveDataEvent: TReceiveDataEvent;
  583. begin
  584.     Result := FOnReceiveData;
  585. end;
  586.  
  587. procedure TVSSComm32.SetReceiveDataEvent( AReceiveDataEvent: TReceiveDataEvent );
  588. begin
  589.     FOnReceiveData := AReceiveDataEvent;
  590. end;
  591.  
  592. function TVSSComm32.GetRequestHangupEvent: TNotifyEvent;
  593. begin
  594.     Result := FOnRequestHangup;
  595. end;
  596.  
  597. procedure TVSSComm32.SetRequestHangupEvent( ARequestHangupEvent: TNotifyEvent );
  598. begin
  599.     FOnRequestHangup := ARequestHangupEvent;
  600. end;
  601.  
  602.  
  603. (******************************************************************************)
  604. //                                            READ THREAD
  605. (******************************************************************************)
  606.  
  607. //
  608. //  PROCEDURE: TReadThread.Execute
  609. //
  610. //  PURPOSE: This is the starting point for the Read Thread.
  611. //
  612. //  PARAMETERS:
  613. //    None.
  614. //
  615. //  RETURN VALUE:
  616. //    None.
  617. //
  618. //  COMMENTS:
  619. //
  620. //    The Read Thread uses overlapped ReadFile and sends any data
  621. //    read from the comm port to the VSSComm32Window.  This is
  622. //    eventually done through a PostMessage so that the Read Thread
  623. //    is never away from the comm port very long.  This also provides
  624. //    natural desynchronization between the Read thread and the UI.
  625. //
  626. //    If the CloseEvent object is signaled, the Read Thread exits.
  627. //
  628. //      Separating the Read and Write threads is natural for a application
  629. //    where there is no need for synchronization between
  630. //    reading and writing.  However, if there is such a need (for example,
  631. //    most file transfer algorithms synchronize the reading and writing),
  632. //    then it would make a lot more sense to have a single thread to handle
  633. //    both reading and writing.
  634. //
  635. //
  636. procedure TReadThread.Execute;
  637. var
  638.      szInputBuffer:    array[0..INPUTBUFFERSIZE-1] of Char;
  639.      nNumberOfBytesRead:    DWORD;
  640.  
  641.      HandlesToWaitFor:    array[0..2] of THandle;
  642.      dwHandleSignaled:    DWORD;
  643.  
  644.      fdwEvtMask:            DWORD;
  645.  
  646.      // Needed for overlapped I/O (ReadFile)
  647.      overlappedRead:        TOverlapped;
  648.  
  649.      // Needed for overlapped Comm Event handling.
  650.      overlappedCommEvent:    TOverlapped;
  651. label
  652.     EndReadThread;
  653. begin
  654.  
  655.     FillChar( overlappedRead, Sizeof(overlappedRead), 0 );
  656.     FillChar( overlappedCommEvent, Sizeof(overlappedCommEvent), 0 );
  657.  
  658.     // Lets put an event in the Read overlapped structure.
  659.     overlappedRead.hEvent := CreateEvent( nil, True, True, nil);
  660.     if overlappedRead.hEvent = 0 then
  661.     begin
  662.          LogDebugLastError( GetLastError, 'Unable to CreateEvent: ' );
  663.          PostHangupCall;
  664.          goto EndReadThread;
  665.     end;
  666.  
  667.     // And an event for the CommEvent overlapped structure.
  668.     overlappedCommEvent.hEvent := CreateEvent( nil, True, True, nil);
  669.     if overlappedCommEvent.hEvent = 0 then
  670.     begin
  671.          LogDebugLastError( GetLastError, 'Unable to CreateEvent: ' );
  672.          PostHangupCall();
  673.          goto EndReadThread;
  674.     end;
  675.  
  676.     // We will be waiting on these objects.
  677.     HandlesToWaitFor[0] := hCloseEvent;
  678.     HandlesToWaitFor[1] := overlappedCommEvent.hEvent;
  679.     HandlesToWaitFor[2] := overlappedRead.hEvent;
  680.  
  681.  
  682.     // Setup CommEvent handling.
  683.  
  684.     // Set the comm mask so we receive error signals.
  685.     if not SetCommMask(hCommFile, EV_ERR) then
  686.     begin
  687.         LogDebugLastError( GetLastError, 'Unable to SetCommMask: ' );
  688.         PostHangupCall;
  689.         goto EndReadThread;
  690.     end;
  691.  
  692.     // Start waiting for CommEvents (Errors)
  693.     if not SetupCommEvent( @overlappedCommEvent,  fdwEvtMask ) then
  694.     begin
  695.         LogDebugLastError( GetLastError, 'Unable to SetupCommEvent1: ' );
  696.         PostHangupCall;
  697.         goto EndReadThread;
  698.     end;
  699.  
  700.     // Start waiting for Read events.
  701.     if not SetupReadEvent( @overlappedRead,
  702.                     szInputBuffer, INPUTBUFFERSIZE,
  703.                      nNumberOfBytesRead ) then
  704.     begin
  705.         LogDebugLastError( GetLastError, 'Unable to SetupReadEvent: ' );
  706.         PostHangupCall;
  707.         goto EndReadThread;
  708.     end;
  709.  
  710.     // Keep looping until we break out.
  711.     while True do
  712.     begin
  713.         // Wait until some event occurs (data to read; error; stopping).
  714.         dwHandleSignaled :=
  715.               WaitForMultipleObjects(3, @HandlesToWaitFor,
  716.                     False, INFINITE);
  717.  
  718.          // Which event occured?
  719.         case dwHandleSignaled of
  720.             WAIT_OBJECT_0:     // Signal to end the thread.
  721.             begin
  722.                 // Time to exit.
  723.                 OutputDebugString( 'Time to Exit' );
  724.                 goto EndReadThread;
  725.             end;
  726.  
  727.             WAIT_OBJECT_0 + 1: // CommEvent signaled.
  728.             begin
  729.                 // Handle the CommEvent.
  730.                 if not HandleCommEvent( @overlappedCommEvent,  fdwEvtMask, TRUE ) then
  731.                 begin
  732.                     PostHangupCall;
  733.                     LogDebugLastError( GetLastError, 'Unable HandleCommEvent: ' );
  734.                     goto EndReadThread;
  735.                 end;
  736.  
  737.                 // Start waiting for the next CommEvent.
  738.                 if not SetupCommEvent( @overlappedCommEvent,  fdwEvtMask ) then
  739.                 begin
  740.                     PostHangupCall;
  741.                     LogDebugLastError( GetLastError, 'Unable to SetupCommEvent2: ' );
  742.                     goto EndReadThread;
  743.                 end;
  744.                 {break;??}
  745.             end;
  746.  
  747.             WAIT_OBJECT_0 + 2: // Read Event signaled.
  748.             begin
  749.                 // Get the new data!
  750.                 if not HandleReadEvent( @overlappedRead,
  751.                                     szInputBuffer, INPUTBUFFERSIZE,
  752.                                      nNumberOfBytesRead ) then
  753.                 begin
  754.                     PostHangupCall;
  755.                     LogDebugLastError( GetLastError, 'Unable to HandleReadEvent: ' );
  756.                     goto EndReadThread;
  757.                 end;
  758.  
  759.                 // Wait for more new data.
  760.                 if not SetupReadEvent( @overlappedRead,
  761.                                     szInputBuffer, INPUTBUFFERSIZE,
  762.                                      nNumberOfBytesRead ) then
  763.                 begin
  764.                     PostHangupCall;
  765.                     goto EndReadThread;
  766.                 end;
  767.                 {break;}
  768.             end;
  769.  
  770.             WAIT_FAILED:       // Wait failed.  Shouldn't happen.
  771.             begin
  772.                 LogDebugLastError( GetLastError, 'Read WAIT_FAILED: ' );
  773.                 PostHangupCall;
  774.                 goto EndReadThread;
  775.             end;
  776.  
  777.             else    // This case should never occur.
  778.             begin
  779.                 LogDebugInfo( PChar('Unexpected Wait return value '+
  780.                             IntToStr(dwHandleSignaled)) );
  781.                 PostHangupCall;
  782.                 goto EndReadThread;
  783.             end;
  784.         end; {case dwHandleSignaled}
  785.     end; {while True}
  786.  
  787.     // Time to clean up Read Thread.
  788.  EndReadThread:
  789.  
  790.     LogDebugInfo( 'Read thread shutting down' );
  791.     PurgeComm( hCommFile, PURGE_RXABORT + PURGE_RXCLEAR );
  792.     CloseHandle( overlappedRead.hEvent );
  793.     CloseHandle( overlappedCommEvent.hEvent );
  794. end; {TReadThread.Execute}
  795.  
  796. //
  797. //  FUNCTION: SetupReadEvent(LPOVERLAPPED, LPSTR, DWORD, LPDWORD)
  798. //
  799. //  PURPOSE: Sets up an overlapped ReadFile
  800. //
  801. //  PARAMETERS:
  802. //    lpOverlappedRead      - address of overlapped structure to use.
  803. //    lpszInputBuffer       - Buffer to place incoming bytes.
  804. //    dwSizeofBuffer        - size of lpszInputBuffer.
  805. //    lpnNumberOfBytesRead  - address of DWORD to place the number of read bytes.
  806. //
  807. //  RETURN VALUE:
  808. //    TRUE if able to successfully setup the ReadFile.  FALSE if there
  809. //    was a failure setting up or if the CloseEvent object was signaled.
  810. //
  811. //  COMMENTS:
  812. //
  813. //    This function is a helper function for the Read Thread.  This
  814. //    function sets up the overlapped ReadFile so that it can later
  815. //    be waited on (or more appropriatly, so the event in the overlapped
  816. //    structure can be waited upon).  If there is data waiting, it is
  817. //    handled and the next ReadFile is initiated.
  818. //    Another possible reason for returning FALSE is if the comm port
  819. //    is closed by the service provider.
  820. //
  821. //
  822. //
  823. function TReadThread.SetupReadEvent( lpOverlappedRead: POverlapped;
  824.      lpszInputBuffer: LPSTR; dwSizeofBuffer: DWORD;
  825.      var lpnNumberOfBytesRead: DWORD ): Boolean;
  826. var
  827.      dwLastError: DWORD;
  828. label
  829.     StartSetupReadEvent;
  830. begin
  831.  
  832. StartSetupReadEvent:
  833.  
  834.     Result := False;
  835.     // Make sure the CloseEvent hasn't been signaled yet.
  836.     // Check is needed because this function is potentially recursive.
  837.     if WAIT_TIMEOUT <> WaitForSingleObject(hCloseEvent,0) then
  838.          Exit;
  839.  
  840.     // Start the overlapped ReadFile.
  841.     if ReadFile( hCommFile,
  842.               lpszInputBuffer^, dwSizeofBuffer,
  843.               lpnNumberOfBytesRead, lpOverlappedRead ) then
  844.     begin
  845.          // This would only happen if there was data waiting to be read.
  846.  
  847.         LogDebugInfo( 'Data waiting for ReadFile: ');
  848.  
  849.          // Handle the data.
  850.         if not HandleReadData( lpszInputBuffer, lpnNumberOfBytesRead ) then
  851.             Exit;
  852.  
  853.          // Start waiting for more data.
  854.         goto StartSetupReadEvent;
  855.     end;
  856.  
  857.     // ReadFile failed.  Expected because of overlapped I/O.
  858.     dwLastError := GetLastError;
  859.  
  860.  
  861.     // LastError was ERROR_IO_PENDING, as expected.
  862.     if dwLastError = ERROR_IO_PENDING then
  863.     begin
  864.          LogDebugInfo( 'Waiting for data from comm connection.' );
  865.          Result := True;
  866.          Exit;
  867.     end;
  868.  
  869.     // Its possible for this error to occur if the
  870.     // service provider has closed the port.  Time to end.
  871.     if dwLastError = ERROR_INVALID_HANDLE then
  872.     begin
  873.          LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  874.               'Likely that the Service Provider has closed the port.' );
  875.          Exit;
  876.     end;
  877.  
  878.     // Unexpected error. No idea what could cause this to happen.
  879.     LogDebugLastError( dwLastError, 'Unexpected ReadFile error: ' );
  880.  
  881.     PostHangupCall;
  882. end; {TReadThread.SetupReadEvent}
  883.  
  884. //
  885. //  FUNCTION: HandleReadData(LPCSTR, DWORD)
  886. //
  887. //  PURPOSE: Deals with data after its been read from the comm file.
  888. //
  889. //  PARAMETERS:
  890. //    lpszInputBuffer  - Buffer to place incoming bytes.
  891. //    dwSizeofBuffer   - size of lpszInputBuffer.
  892. //
  893. //  RETURN VALUE:
  894. //    TRUE if able to successfully handle the data.
  895. //    FALSE if unable to allocate memory or handle the data.
  896. //
  897. //  COMMENTS:
  898. //
  899. //    This function is yet another helper function for the Read Thread.
  900. //    It LocalAlloc()s a buffer, copies the new data to this buffer and
  901. //    calls PostWriteToDisplayCtl to let the EditCtls module deal with
  902. //    the data.  Its assumed that PostWriteToDisplayCtl posts the message
  903. //    rather than dealing with it right away so that the Read Thread
  904. //    is free to get right back to waiting for data.  Its also assumed
  905. //    that the EditCtls module is responsible for LocalFree()ing the
  906. //    pointer that is passed on.
  907. //
  908. //
  909. function TReadThread.HandleReadData( lpszInputBuffer: LPCSTR; dwSizeofBuffer: DWORD ): Boolean;
  910. var
  911.     lpszPostedBytes: LPSTR;
  912.     tempstr:                string;
  913. begin
  914.     Result := False;
  915.      // If we got data and didn't just time out empty...
  916.     if dwSizeofBuffer <> 0 then
  917.     begin
  918.         tempstr := lpszInputBuffer;
  919.  
  920.           // Do something with the bytes read.
  921.         LogDebugInfo( 'Got something from Comm port!!!' );
  922.  
  923.         lpszPostedBytes := PChar( LocalAlloc( LPTR, dwSizeofBuffer+1 ) );
  924.  
  925.         if lpszPostedBytes = nil{NULL} then
  926.         begin
  927.             LogDebugLastError( GetLastError, 'LocalAlloc: ' );
  928.             Exit;
  929.         end;
  930.  
  931.         Move( lpszInputBuffer^, lpszPostedBytes^, dwSizeofBuffer );
  932.         lpszPostedBytes[dwSizeofBuffer] := #0;
  933.  
  934.         Result := ReceiveData( lpszPostedBytes, dwSizeofBuffer );
  935.     end;
  936. end; {TReadThread.HandleReadData}
  937.  
  938. //
  939. //  FUNCTION: HandleReadEvent(LPOVERLAPPED, LPSTR, DWORD, LPDWORD)
  940. //
  941. //  PURPOSE: Retrieves and handles data when there is data ready.
  942. //
  943. //  PARAMETERS:
  944. //    lpOverlappedRead      - address of overlapped structure to use.
  945. //    lpszInputBuffer       - Buffer to place incoming bytes.
  946. //    dwSizeofBuffer        - size of lpszInputBuffer.
  947. //    lpnNumberOfBytesRead  - address of DWORD to place the number of read bytes.
  948. //
  949. //  RETURN VALUE:
  950. //    TRUE if able to successfully retrieve and handle the available data.
  951. //    FALSE if unable to retrieve or handle the data.
  952. //
  953. //  COMMENTS:
  954. //
  955. //    This function is another helper function for the Read Thread.  This
  956. //    is the function that is called when there is data available after
  957. //    an overlapped ReadFile has been setup.  It retrieves the data and
  958. //    handles it.
  959. //
  960. //
  961. function TReadThread.HandleReadEvent( lpOverlappedRead: POverlapped;
  962.      lpszInputBuffer: LPSTR; dwSizeofBuffer: DWORD;
  963.      var lpnNumberOfBytesRead: DWORD ): Boolean;
  964. var
  965.     dwLastError: DWORD;
  966. begin
  967.     Result := False;
  968.     if GetOverlappedResult( hCommFile,
  969.             lpOverlappedRead^, lpnNumberOfBytesRead, False ) then
  970.     begin
  971.         Result := HandleReadData( lpszInputBuffer, lpnNumberOfBytesRead );
  972.         Exit;
  973.     end;
  974.  
  975.     // Error in GetOverlappedResult; handle it.
  976.  
  977.     dwLastError := GetLastError;
  978.  
  979.     // Its possible for this error to occur if the
  980.     // service provider has closed the port.  Time to end.
  981.     if dwLastError = ERROR_INVALID_HANDLE then
  982.     begin
  983.         LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  984.                 'Likely that the Service Provider has closed the port.' );
  985.         Exit;
  986.     end;
  987.  
  988.     LogDebugLastError( dwLastError,
  989.           'Unexpected GetOverlappedResult Read Error: ' );
  990.  
  991.     PostHangupCall;
  992. end; {TReadThread.HandleReadEvent}
  993.  
  994. //
  995. //  FUNCTION: SetupCommEvent(LPOVERLAPPED, LPDWORD)
  996. //
  997. //  PURPOSE: Sets up the overlapped WaitCommEvent call.
  998. //
  999. //  PARAMETERS:
  1000. //    lpOverlappedCommEvent - Pointer to the overlapped structure to use.
  1001. //    lpfdwEvtMask          - Pointer to DWORD to received Event data.
  1002. //
  1003. //  RETURN VALUE:
  1004. //    TRUE if able to successfully setup the WaitCommEvent.
  1005. //    FALSE if unable to setup WaitCommEvent, unable to handle
  1006. //    an existing outstanding event or if the CloseEvent has been signaled.
  1007. //
  1008. //  COMMENTS:
  1009. //
  1010. //    This function is a helper function for the Read Thread that sets up
  1011. //    the WaitCommEvent so we can deal with comm events (like Comm errors)
  1012. //    if they occur.
  1013. //
  1014. //
  1015. function TReadThread.SetupCommEvent( lpOverlappedCommEvent: POverlapped;
  1016.      var lpfdwEvtMask: DWORD ): Boolean;
  1017. var
  1018.     dwLastError: DWORD;
  1019. label
  1020.     StartSetupCommEvent;
  1021. begin
  1022.  
  1023.     Result := False;
  1024. StartSetupCommEvent:
  1025.  
  1026.      // Make sure the CloseEvent hasn't been signaled yet.
  1027.      // Check is needed because this function is potentially recursive.
  1028.     if WAIT_TIMEOUT <> WaitForSingleObject( hCloseEvent,0 ) then
  1029.         Exit;
  1030.  
  1031.     // Start waiting for Comm Errors.
  1032.     if WaitCommEvent( hCommFile, lpfdwEvtMask, lpOverlappedCommEvent ) then
  1033.     begin
  1034.         // This could happen if there was an error waiting on the
  1035.         // comm port.  Lets try and handle it.
  1036.  
  1037.         LogDebugInfo( 'Event (Error) waiting before WaitCommEvent.' );
  1038.  
  1039.         if not HandleCommEvent( nil, lpfdwEvtMask, False ) then
  1040.         {??? GetOverlappedResult does not handle "NIL" as defined by Borland}
  1041.             Exit;
  1042.  
  1043.         // What could cause infinite recursion at this point?
  1044.         goto StartSetupCommEvent;
  1045.     end;
  1046.  
  1047.     // We expect ERROR_IO_PENDING returned from WaitCommEvent
  1048.     // because we are waiting with an overlapped structure.
  1049.  
  1050.     dwLastError := GetLastError;
  1051.  
  1052.     // LastError was ERROR_IO_PENDING, as expected.
  1053.     if dwLastError = ERROR_IO_PENDING then
  1054.     begin
  1055.         LogDebugInfo( 'Waiting for a CommEvent (Error) to occur.' );
  1056.         Result := True;
  1057.         Exit
  1058.     end;
  1059.  
  1060.     // Its possible for this error to occur if the
  1061.     // service provider has closed the port.  Time to end.
  1062.     if dwLastError = ERROR_INVALID_HANDLE then
  1063.     begin
  1064.         LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  1065.                 'Likely that the Service Provider has closed the port.' );
  1066.         Exit;
  1067.     end;
  1068.  
  1069.     // Unexpected error. No idea what could cause this to happen.
  1070.     LogDebugLastError( dwLastError, 'Unexpected WaitCommEvent error: ' );
  1071. end; {TReadThread.SetupCommEvent}
  1072.  
  1073. //
  1074. //  FUNCTION: HandleCommEvent(LPOVERLAPPED, LPDWORD, BOOL)
  1075. //
  1076. //  PURPOSE: Handle an outstanding Comm Event.
  1077. //
  1078. //  PARAMETERS:
  1079. //    lpOverlappedCommEvent - Pointer to the overlapped structure to use.
  1080. //    lpfdwEvtMask          - Pointer to DWORD to received Event data.
  1081. //     fRetrieveEvent       - Flag to signal if the event needs to be
  1082. //                            retrieved, or has already been retrieved.
  1083. //
  1084. //  RETURN VALUE:
  1085. //    TRUE if able to handle a Comm Event.
  1086. //    FALSE if unable to setup WaitCommEvent, unable to handle
  1087. //    an existing outstanding event or if the CloseEvent has been signaled.
  1088. //
  1089. //  COMMENTS:
  1090. //
  1091. //    This function is a helper function for the Read Thread that (if
  1092. //    fRetrieveEvent == TRUE) retrieves an outstanding CommEvent and
  1093. //    deals with it.  The only event that should occur is an EV_ERR event,
  1094. //    signalling that there has been an error on the comm port.
  1095. //
  1096. //    Normally, comm errors would not be put into the normal data stream
  1097. //    as this sample is demonstrating.  Putting it in a status bar would
  1098. //    be more appropriate for a real application.
  1099. //
  1100. //
  1101. function TReadThread.HandleCommEvent( lpOverlappedCommEvent: POverlapped;
  1102.      var lpfdwEvtMask: DWORD; fRetrieveEvent: Boolean ): Boolean;
  1103. var
  1104.     dwDummy:            DWORD;
  1105.     lpszOutput:        LPSTR;
  1106.     szError:            array[0..127] of Char;
  1107.     dwErrors,
  1108.     nOutput,
  1109.     dwLastError:    DWORD;
  1110. begin
  1111.     Result := False;
  1112.  
  1113.     szError[0] := #0;
  1114.  
  1115.     lpszOutput := PChar(LocalAlloc( LPTR, 256 ));
  1116.     if lpszOutput = nil{NULL} then
  1117.     begin
  1118.         LogDebugLastError( GetLastError, 'LocalAlloc: ' );
  1119.         Exit;
  1120.     end;
  1121.  
  1122.     // If this fails, it could be because the file was closed (and I/O is
  1123.     // finished) or because the overlapped I/O is still in progress.  In
  1124.     // either case (or any others) its a bug and return FALSE.
  1125.     if fRetrieveEvent then
  1126.         if not GetOverlappedResult( hCommFile,
  1127.                      lpOverlappedCommEvent^, dwDummy, False ) then
  1128.         begin
  1129.             dwLastError := GetLastError;
  1130.  
  1131.             // Its possible for this error to occur if the
  1132.             // service provider has closed the port.  Time to end.
  1133.             if dwLastError = ERROR_INVALID_HANDLE then
  1134.             begin
  1135.                 LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  1136.                           'Likely that the Service Provider has closed the port.' );
  1137.                 Exit;
  1138.             end;
  1139.  
  1140.             LogDebugLastError( dwLastError,
  1141.                      'Unexpected GetOverlappedResult for WaitCommEvent: ' );
  1142.             Exit;
  1143.         end;
  1144.  
  1145.     // Was the event an error?
  1146.     if (lpfdwEvtMask and EV_ERR) <> 0 then
  1147.     begin
  1148.         // Which error was it?
  1149.         if not ClearCommError( hCommFile, dwErrors, nil ) then
  1150.         begin
  1151.             dwLastError := GetLastError;
  1152.  
  1153.             // Its possible for this error to occur if the
  1154.             // service provider has closed the port.  Time to end.
  1155.             if dwLastError = ERROR_INVALID_HANDLE then
  1156.             begin
  1157.                 LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  1158.                       'Likely that the Service Provider has closed the port.' );
  1159.                 Exit;
  1160.             end;
  1161.  
  1162.             LogDebugLastError( GetLastError,'ClearCommError: ' );
  1163.             Exit;
  1164.         end;
  1165.  
  1166.         // Its possible that multiple errors occured and were handled
  1167.         // in the last ClearCommError.  Because all errors were signaled
  1168.         // individually, but cleared all at once, pending comm events
  1169.         // can yield EV_ERR while dwErrors equals 0.  Ignore this event.
  1170.         if dwErrors = 0 then
  1171.             strcat( szError, 'NULL Error' );
  1172.  
  1173.         if (dwErrors and CE_FRAME) <> 0 then
  1174.         begin
  1175.             if szError[0] <> #0 then
  1176.                 strcat( szError, ' and ' );
  1177.  
  1178.             strcat( szError,'CE_FRAME' );
  1179.         end;
  1180.  
  1181.         if (dwErrors and CE_OVERRUN) <> 0 then
  1182.         begin
  1183.             if szError[0] <> #0 then
  1184.                 strcat(szError, ' and ' );
  1185.  
  1186.             strcat( szError, 'CE_OVERRUN' );
  1187.         end;
  1188.  
  1189.         if (dwErrors and CE_RXPARITY) <> 0 then
  1190.         begin
  1191.             if szError[0] <> #0 then
  1192.                 strcat( szError, ' and ' );
  1193.  
  1194.             strcat( szError, 'CE_RXPARITY' );
  1195.         end;
  1196.  
  1197.         if (dwErrors and not (CE_FRAME + CE_OVERRUN + CE_RXPARITY)) <> 0 then
  1198.         begin
  1199.             if szError[0] <> #0 then
  1200.                 strcat( szError, ' and ' );
  1201.  
  1202.             strcat( szError, 'EV_ERR Unknown EvtMask' );
  1203.         end;
  1204.  
  1205.         nOutput := wsprintf(lpszOutput,
  1206.                 PChar('Comm Event: '+szError+', EvtMask = '+IntToStr(dwErrors)) );
  1207.  
  1208.         ReceiveData( lpszOutput, nOutput );
  1209.         Result := True;
  1210.         Exit
  1211.     end;
  1212.  
  1213.     // Should not have gotten here.  Only interested in ERR conditions.
  1214.  
  1215.     LogDebugInfo( PChar('Unexpected comm event '+IntToStr(lpfdwEvtMask)) );
  1216. end; {TReadThread.HandleCommEvent}
  1217.  
  1218. function TReadThread.ReceiveData( lpNewString: LPSTR; dwSizeofNewString: DWORD ): BOOL;
  1219. begin
  1220.     Result := PostMessage( hVSSComm32Window, PWM_GOTCOMMDATA,
  1221.           WPARAM(dwSizeofNewString), LPARAM(lpNewString) );
  1222. end;
  1223.  
  1224. procedure TReadThread.PostHangupCall;
  1225. begin
  1226.     PostMessage( hVSSComm32Window, PWM_REQUESTHANGUP, 0, 0 );
  1227. end;
  1228.  
  1229. (******************************************************************************)
  1230. //                                            WRITE THREAD
  1231. (******************************************************************************)
  1232.  
  1233. //
  1234. //  PROCEDURE: TWriteThread.Execute
  1235. //
  1236. //  PURPOSE: The starting point for the Write thread.
  1237. //
  1238. //  PARAMETERS:
  1239. //    lpvParam - unused.
  1240. //
  1241. //  RETURN VALUE:
  1242. //    DWORD - unused.
  1243. //
  1244. //  COMMENTS:
  1245. //
  1246. //    The Write thread uses a PeekMessage loop to wait for a string to write,
  1247. //    and when it gets one, it writes it to the Comm port.  If the CloseEvent
  1248. //    object is signaled, then it exits.  The use of messages to tell the
  1249. //    Write thread what to write provides a natural desynchronization between
  1250. //    the UI and the Write thread.
  1251. //
  1252. //
  1253. procedure TWriteThread.Execute;
  1254. var
  1255.      msg:    TMsg;
  1256.      dwHandleSignaled:    DWORD;
  1257.      overlappedWrite:        TOverLapped;
  1258. label
  1259.     EndWriteThread;
  1260. begin
  1261.  
  1262.      // Needed for overlapped I/O.
  1263.      FillChar( overlappedWrite, SizeOf(overlappedWrite), 0 );  {0, 0, 0, 0, NULL}
  1264.  
  1265.      overlappedWrite.hEvent := CreateEvent( nil, True, True, nil );
  1266.      if overlappedWrite.hEvent = 0 then
  1267.      begin
  1268.           LogDebugLastError( GetLastError, 'Unable to CreateEvent: ' );
  1269.           PostHangupCall;
  1270.           goto EndWriteThread;
  1271.      end;
  1272.  
  1273.      // This is the main loop.  Loop until we break out.
  1274.      while True do
  1275.      begin
  1276.           if not PeekMessage( msg, 0, 0, 0, PM_REMOVE ) then
  1277.           begin
  1278.                 // If there are no messages pending, wait for a message or
  1279.                 // the CloseEvent.
  1280.                 dwHandleSignaled :=
  1281.                      MsgWaitForMultipleObjects(1, hCloseEvent, False,
  1282.                           INFINITE, QS_ALLINPUT);
  1283.  
  1284.                 case dwHandleSignaled of
  1285.                      WAIT_OBJECT_0:     // CloseEvent signaled!
  1286.                      begin
  1287.                           // Time to exit.
  1288.                           goto EndWriteThread;
  1289.                      end;
  1290.  
  1291.                      WAIT_OBJECT_0 + 1: // New message was received.
  1292.                      begin
  1293.                           // Get the message that woke us up by looping again.
  1294.                           continue;
  1295.                      end;
  1296.  
  1297.                      WAIT_FAILED:       // Wait failed.  Shouldn't happen.
  1298.                      begin
  1299.                           LogDebugLastError( GetLastError, 'Write WAIT_FAILED: ' );
  1300.                           PostHangupCall;
  1301.                           goto EndWriteThread;
  1302.                      end;
  1303.  
  1304.                      else                // This case should never occur.
  1305.                      begin
  1306.                           LogDebugInfo( PChar('Unexpected Wait return value '
  1307.                                                         +IntToStr(dwHandleSignaled)) );
  1308.                           PostHangupCall;
  1309.                           goto EndWriteThread;
  1310.                      end;
  1311.                 end;
  1312.           end;
  1313.  
  1314.           // Make sure the CloseEvent isn't signaled while retrieving messages.
  1315.           if WAIT_TIMEOUT <> WaitForSingleObject(hCloseEvent,0) then
  1316.                 goto EndWriteThread;
  1317.  
  1318.           // Process the message.
  1319.  
  1320.           // This could happen if a dialog is created on this thread.
  1321.           // This doesn't occur in this sample, but might if modified.
  1322.           if msg.hwnd <> 0{NULL} then
  1323.           begin
  1324.                 TranslateMessage(msg);
  1325.                 DispatchMessage(msg);
  1326.  
  1327.                 continue;
  1328.           end;
  1329.  
  1330.           // Handle the message.
  1331.           case msg.message of
  1332.                 PWM_COMMWRITE:  // New string to write to Comm port.
  1333.                 begin
  1334.                      LogDebugInfo( 'Writing to comm port' );
  1335.  
  1336.                      // Write the string to the comm port.  HandleWriteData
  1337.                      // does not return until the whole string has been written,
  1338.                      // an error occurs or until the CloseEvent is signaled.
  1339.                      if not HandleWriteData( @overlappedWrite,
  1340.                                 PChar(msg.lParam), DWORD(msg.wParam) ) then
  1341.                      begin
  1342.                           // If it failed, either we got a signal to end or there
  1343.                           // really was a failure.
  1344.  
  1345.                           LocalFree( HLOCAL(msg.lParam) );
  1346.                           goto EndWriteThread;
  1347.                      end;
  1348.  
  1349.                      // Data was sent in a LocalAlloc()d buffer.  Must free it.
  1350.                      LocalFree( HLOCAL(msg.lParam) );
  1351.                 end;
  1352.  
  1353.                 // What other messages could the thread get?
  1354.                 else
  1355.                 begin
  1356.                      LogDebugInfo( PChar('Unexpected message posted to Write thread: '+
  1357.                           IntToStr(msg.message)) );
  1358.                      {break;}
  1359.                 end;
  1360.           end; {case}
  1361.      end; {main loop}
  1362.  
  1363.      // Thats the end.  Now clean up.
  1364.   EndWriteThread:
  1365.  
  1366.      LogDebugInfo( 'Write thread shutting down' );
  1367.  
  1368.      PurgeComm(hCommFile, PURGE_TXABORT + PURGE_TXCLEAR);
  1369.  
  1370.      CloseHandle(overlappedWrite.hEvent);
  1371. end; {TWriteThread.Execute}
  1372.  
  1373.  
  1374. //
  1375. //  FUNCTION: HandleWriteData(LPOVERLAPPED, LPCSTR, DWORD)
  1376. //
  1377. //  PURPOSE: Writes a given string to the comm file handle.
  1378. //
  1379. //  PARAMETERS:
  1380. //    lpOverlappedWrite      - Overlapped structure to use in WriteFile
  1381. //    pDataToWrite      - String to write.
  1382. //    dwNumberOfBytesToWrite - Length of String to write.
  1383. //
  1384. //  RETURN VALUE:
  1385. //    TRUE if all bytes were written.  False if there was a failure to
  1386. //    write the whole string.
  1387. //
  1388. //  COMMENTS:
  1389. //
  1390. //    This function is a helper function for the Write Thread.  It
  1391. //    is this call that actually writes a string to the comm file.
  1392. //    Note that this call blocks and waits for the Write to complete
  1393. //    or for the CloseEvent object to signal that the thread should end.
  1394. //    Another possible reason for returning FALSE is if the comm port
  1395. //    is closed by the service provider.
  1396. //
  1397. //
  1398. function TWriteThread.HandleWriteData( lpOverlappedWrite: POverlapped;
  1399.      pDataToWrite: PChar; dwNumberOfBytesToWrite: DWORD): Boolean;
  1400. var
  1401.     dwLastError,
  1402.  
  1403.     dwNumberOfBytesWritten,
  1404.     dwWhereToStartWriting,
  1405.  
  1406.     dwHandleSignaled:    DWORD;
  1407.     HandlesToWaitFor: array[0..1] of THandle;
  1408. begin
  1409.     dwNumberOfBytesWritten := 0;
  1410.     dwWhereToStartWriting := 0; // Start at the beginning.
  1411.  
  1412.     HandlesToWaitFor[0] := hCloseEvent;
  1413.     HandlesToWaitFor[1] := lpOverlappedWrite^.hEvent;
  1414.  
  1415.      // Keep looping until all characters have been written.
  1416.      repeat
  1417.           // Start the overlapped I/O.
  1418.           if not WriteFile(hCommFile,
  1419.                      pDataToWrite[ dwWhereToStartWriting ],
  1420.                      dwNumberOfBytesToWrite, dwNumberOfBytesWritten,
  1421.                      lpOverlappedWrite) then
  1422.           begin
  1423.                 // WriteFile failed.  Expected; lets handle it.
  1424.                 dwLastError := GetLastError;
  1425.  
  1426.                 // Its possible for this error to occur if the
  1427.                 // service provider has closed the port.  Time to end.
  1428.                 if (dwLastError = ERROR_INVALID_HANDLE) then
  1429.                 begin
  1430.                      LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  1431.                           'Likely that the Service Provider has closed the port.' );
  1432.                      Result := False;
  1433.                      Exit;
  1434.                 end;
  1435.  
  1436.                 // Unexpected error.  No idea what.
  1437.                 if dwLastError <> ERROR_IO_PENDING then
  1438.                 begin
  1439.                      LogDebugLastError( dwLastError, 'Error to writing to CommFile' );
  1440.  
  1441.                      LogDebugInfo( 'Closing TAPI' );
  1442.                      PostHangupCall;
  1443.                      Result := False;
  1444.                      Exit;
  1445.                 end;
  1446.  
  1447.                 // This is the expected ERROR_IO_PENDING case.
  1448.  
  1449.  
  1450.                 // Wait for either overlapped I/O completion,
  1451.                 // or for the CloseEvent to get signaled.
  1452.                 dwHandleSignaled :=
  1453.                      WaitForMultipleObjects(2, @HandlesToWaitFor,
  1454.                           False, INFINITE);
  1455.  
  1456.                 case dwHandleSignaled of
  1457.                      WAIT_OBJECT_0:     // CloseEvent signaled!
  1458.                      begin
  1459.                           // Time to exit.
  1460.                           Result := False;
  1461.                           Exit;
  1462.                      end;
  1463.  
  1464.                      WAIT_OBJECT_0 + 1: // Wait finished.
  1465.                      begin
  1466.                           // Time to get the results of the WriteFile
  1467.                      end;
  1468.  
  1469.                      WAIT_FAILED: // Wait failed.  Shouldn't happen.
  1470.                      begin
  1471.                           LogDebugLastError( GetLastError, 'Write WAIT_FAILED: ' );
  1472.                           PostHangupCall;
  1473.                           Result := False;
  1474.                           Exit
  1475.                      end;
  1476.  
  1477.                      else // This case should never occur.
  1478.                      begin
  1479.                           LogDebugInfo( PChar('Unexpected Wait return value '+
  1480.                                                     IntToStr(dwHandleSignaled)) );
  1481.                           PostHangupCall;
  1482.                           Result := False;
  1483.                           Exit
  1484.                      end;
  1485.                 end; {case}
  1486.  
  1487.                 if not GetOverlappedResult(hCommFile,
  1488.                             lpOverlappedWrite^,
  1489.                             dwNumberOfBytesWritten, TRUE) then
  1490.                 begin
  1491.                      dwLastError := GetLastError();
  1492.  
  1493.                      // Its possible for this error to occur if the
  1494.                      // service provider has closed the port.
  1495.                      if dwLastError = ERROR_INVALID_HANDLE then
  1496.                      begin
  1497.                           LogDebugInfo('ERROR_INVALID_HANDLE, '+
  1498.                                 'Likely that the Service Provider has closed the port.');
  1499.                           Result := False;
  1500.                           Exit;
  1501.                      end;
  1502.  
  1503.                      // No idea what could cause another error.
  1504.                      LogDebugLastError( dwLastError, 'Error writing to CommFile while waiting');
  1505.                      LogDebugInfo('Closing TAPI');
  1506.                      PostHangupCall;
  1507.                      Result := False;
  1508.                      Exit;
  1509.                 end;
  1510.           end; {WriteFile failure}
  1511.  
  1512.           // Some data was written.  Make sure it all got written.
  1513.  
  1514.           Dec( dwNumberOfBytesToWrite, dwNumberOfBytesWritten );
  1515.           Inc( dwWhereToStartWriting, dwNumberOfBytesWritten );
  1516.      until (dwNumberOfBytesToWrite <= 0);  // Write the whole thing!
  1517.  
  1518.      // Wrote the whole string.
  1519.      Result := True;
  1520. end; {TWriteThread.HandleWriteData}
  1521.  
  1522. function TWriteThread.WriteComm( pDataToWrite: LPCSTR; dwSizeofDataToWrite: DWORD ): Boolean;
  1523. begin
  1524.     Result := PostThreadMessage( ThreadID, PWM_COMMWRITE,
  1525.                      WParam(dwSizeofDataToWrite), LParam(pDataToWrite) );
  1526. end;
  1527.  
  1528. procedure TWriteThread.PostHangupCall;
  1529. begin
  1530.     PostMessage( hVSSComm32Window, PWM_REQUESTHANGUP, 0, 0 );
  1531. end;
  1532.  
  1533. (******************************************************************************)
  1534. //                                            DEBUG ROUTINES
  1535. (******************************************************************************)
  1536.  
  1537. //
  1538. //  FUNCTION: LogDebugLastError(..)
  1539. //
  1540. //  PURPOSE: Pretty print a line error to the debugging output.
  1541. //
  1542. //  PARAMETERS:
  1543. //    dwLastError - Actual error code to decipher.
  1544. //    pszPrefix   - String to prepend to the printed message.
  1545. //
  1546. //  RETURN VALUE:
  1547. //    none
  1548. //
  1549. //  COMMENTS:
  1550. //
  1551. //    Note that there is an internal string length limit of
  1552. //    MAXOUTPUTSTRINGLENGTH.  If this length is exceeded,
  1553. //    the behavior will be the same as wsprintf, although
  1554. //    it will be undetectable.  *KEEP szPrefix SHORT!*
  1555. //
  1556. //
  1557. procedure LogDebugLastError( dwLastError: DWORD; szPrefix: LPSTR );
  1558. var
  1559.     szLastError: LPSTR;
  1560.     szOutputLastError: array[0..MAXOUTPUTSTRINGLENGTH-1] of Char;
  1561. begin
  1562.     if szPrefix = nil then
  1563.         szPrefix := '';
  1564.  
  1565.        {    // Pretty print the error.
  1566.     szLastError := szFormatLastError(dwLastError, nil, 0);
  1567.         }
  1568.     // The only reason FormatLastError should fail is "Out of memory".
  1569.     if szLastError = nil then
  1570.     begin
  1571.         wsprintf( szOutputLastError, PChar(szPrefix+'Out of memory') );
  1572.  
  1573.         LogDebugInfo( szOutputLastError );
  1574.  
  1575.         Exit;
  1576.     end;
  1577.  
  1578.     wsprintf( szOutputLastError,
  1579.               PChar(szPrefix+'GetLastError returned: "'+szLastError+'"') );
  1580.  
  1581.     // Pointer returned from FormatLineError *must* be freed!
  1582.     LocalFree( HLOCAL(szLastError) );
  1583.  
  1584.     // Print it!
  1585.     LogDebugInfo( szOutputLastError );
  1586. end; {LogDebugLastError}
  1587.  
  1588. procedure LogDebugInfo( outstr: PChar );
  1589. begin
  1590.     if CommsLogName <> '' then
  1591.         Writeln( CommsLogFile, outstr );
  1592. end; {LogDebugInfo}
  1593.  
  1594. procedure Register;
  1595. begin
  1596.   RegisterComponents('VSS', [TVSSComm32]);
  1597. end;
  1598.  
  1599. end.
  1600.